
import cherrypy
import os
import socket
import sys
import inspect
import random
import urllib
import Image
import math
from xml.dom import minidom
from cherrypy.lib.static import serve_file
#from config import *


class Annotator():
    """Album Annotator - annotating image albums

    For adding predefined image tags (at image level) and
    adding object tags (name/id for reoccurring objects in
    sub-parts of the images).

    Run this CherryPy app and open it in your browser, preferable
    Firefox. Provide a local path to a folder with jpeg images.
    Annotate the images with tags and object tags. The annotations
    will be saved in the image folder in a xml file called
    'annotations.xml'. You can later edit previously added
    annotations by loading the same folder again (given that the
    xml file still is present in that folder).

    Click and drag in each image to annotate a reoccurring object
    with custom name/id. Click on the available tags below the
    image to attach the corresponding tag to the image.

    Annotations are saved/updated in the xml file each time
    you click 'previous', 'index' or 'next'.
    
    Modify the 'classes' variable if you want to customize 
    the tags you can add to images.
    """

    def __init__(self):
        self.classes = {'animal':['cat','dog'], \
                   'scenery':['beach','forest','snow'], \
                   'transportation':['aeroplane','bicycle','car','motorbike']}
        self.header = """
            <!doctype html>
            <head>
            <title></title>
            <link rel="stylesheet" href="/static/design/annotator.css" type="text/css" />
            <link rel="stylesheet" href="/static/design/autocomplete.css" type="text/css" />
            <script type="text/javascript" src="/static/design/jquery-1.3.2.js"></script>
            <script src="/static/design/jquery.Jcrop.js"></script>
            <link rel="stylesheet" href="/static/design/jquery.Jcrop.css" type="text/css" />
            <script type="text/javascript" src="/static/design/autocomplete.js"></script>
            <script type="text/javascript" src="/static/design/annotator.js"></script>
            </head>
            <body>
            """
        self.footer = """
            </body>
            </html>
            """
        self.images = []
        self.history = []
        self.album = ""


    def init(self, album=None):
        self.images = []
        self.history = []
        self.album = ""
        if album:
            self.album = album
            dirList = os.listdir(self.album)
            def filter_jpegs(f):
                return {'jpg':'','JPG':'','jpeg':'','JPEG':''}.has_key(f.split('.')[-1])
            jpegList = filter(filter_jpegs, dirList)
            for jpeg in jpegList:
                size = Image.open(os.path.join(self.album,jpeg)).size
                self.images.append({'file':jpeg, 'width':size[0], 'height':size[1]})

            def get_text(node):
                text = ""
                for child in node.childNodes:
                    if child.nodeType == child.TEXT_NODE:
                        text += child.data
                return text

            try:
                xmlDoc = minidom.parse(os.path.join(self.album,"annotations.xml"))
                if not xmlDoc.documentElement.tagName == "images":
                    raise Exception
                for obj in xmlDoc.documentElement.getElementsByTagName("object"):
                    if not str(get_text(obj)) in self.history:
                        self.history.append(str(get_text(obj)))
            except Exception:
                pass


    def image(self, file, r=None):
        #file = file.replace('\\','/').strip().rpartition('/')[2]
        #return serve_file(os.path.join( self.album , file ))
        return serve_file(os.path.join( self.album , self.images[int(file)]['file'] ))

    image.exposed = True

    def index(self, image=None, tags=None, objects=None, what=None, albumpath=None, page=0):
        html = self.header

        print "image:"+str(image), "tags:"+str(tags), "objects:"+str(objects), "what:"+str(what)

        if not what:
            html += "<div id='init'>"
            html += "<form action='' method='post'>"
            html += "<input id='albumpath' name='albumpath' type='text' value='enter local folder path...' />"
            html += "<a class='button' href='#'>ok</a>"
            html += "<input type='hidden' name='what' value='init' />"
            html += "</form>"
            html += "</div>"
            html += self.footer
            return html

        if what == "init":
            if albumpath:
                print "init!"
                print albumpath
                albumpath = albumpath.replace('\\','/').strip().rstrip('/')
                print albumpath
                self.init(albumpath)
            raise cherrypy.HTTPRedirect("/?what=index")

        if image:
            image = int(image)
            if what == "edit":
                pass
            elif image > 0 and image <= len(self.images):
                if os.path.isfile(os.path.join(self.album,"annotations.xml")):
                    xmlDoc = minidom.parse(os.path.join(self.album,"annotations.xml"))
                    if not xmlDoc.documentElement.tagName == "images":
                        raise Exception
                else:
                    xmlDoc = minidom.getDOMImplementation().createDocument(None, "images", None)
                xmlImages = xmlDoc.documentElement
                def filter_file(f):
                    return f.getAttribute("file") == self.images[image-1]['file']
                xmlResult = filter(filter_file,xmlImages.getElementsByTagName("image"))
                if xmlResult:
                    xmlImage = xmlResult[0]
                else:
                    xmlImage = xmlImages.appendChild(xmlDoc.createElement("image"))
                    xmlImage.setAttribute("file", self.images[image-1]['file'])
                xmlImage.setAttribute("width", str(self.images[image-1]['width']))
                xmlImage.setAttribute("height", str(self.images[image-1]['height']))
                while xmlImage.hasChildNodes():
                    node = xmlImage.firstChild
                    xmlImage.removeChild(node)
                    node.unlink()
                if tags:
                    tgs = tags.split(",")
                    for tg in tgs:
                        xmltg = xmlDoc.createElement("tag")
                        xmltg.appendChild(xmlDoc.createTextNode(tg))
                        xmlImage.appendChild(xmltg)
                if objects:
                    objs = objects.split(",")
                    for obj in objs:
                        dictobj = dict(item.split("=") for item in obj.split("&"))
                        xmlobj = xmlDoc.createElement("object")
                        xmlobj.setAttribute("left",str(dictobj["left"]))
                        xmlobj.setAttribute("right",str(dictobj["right"]))
                        xmlobj.setAttribute("top",str(dictobj["top"]))
                        xmlobj.setAttribute("bottom",str(dictobj["bottom"]))
                        xmlobj.appendChild(xmlDoc.createTextNode(dictobj["id"]))
                        xmlImage.appendChild(xmlobj)
                        if not dictobj["id"] in self.history:
                            self.history.append(dictobj["id"])
                fid = open(os.path.join(self.album,"annotations.xml"),"w")
                #fid.write(xmlDoc.toprettyxml(encoding='utf-8'))
                fid.write(xmlDoc.toxml(encoding='utf-8'))
                #xmlDoc.writexml(fid,encoding='utf-8')
                fid.close()
            if what == "previous":
                image -= 1
            elif what == "index":
                image = 0
            elif what == "next":
                image += 1
        else:
            image = 0

        if image > len(self.images):
            if not len(self.images):
                html += "hmm... no images... <a href='/'>enter local folder path</a>"
            else:
                html += "<div id='next'>"
                html += "<div class='done'>done! thank you!</div>"
                html += "<a class='button previous' href='#'>previous</a>"
                html += "<a class='button index' href='#'>index</a>"
                html += "<form action='' method='post'>"
                html += "<input type='hidden' name='image' value='"+str(image)+"' />"
                html += "<input type='hidden' name='tags' value='' />"
                html += "<input type='hidden' name='objects' value='' />"
                html += "<input type='hidden' name='what' value='next' />"
                html += "<input type='hidden' name='page' value='0' />"
                html += "</form>"
                html += "</div>"
        elif image < 1:
            page = int(page)
            html += "<div id='index'>"
            if not len(self.images):
                html += "hmm... no images... <a href='/'>enter local folder path</a>"
            else:
                for i in range(page*20, min(page*20+20,len(self.images))):
                    html += "<a href='/?image="+str(i+1)+"&what=edit' class='indexthumb'>"
                    html += "<img src='"
                    html += "/image?file="+str(i)+"&r="+str(random.randint(1,999999))#img['file']
                    html += "' height=150 "
                    html += "title='"+self.images[i]['file']+"' />"
                    html += "</a>"
            html += "</div>"

            if len(self.images):
                maxpage = int(math.ceil(len(self.images)/20.0))
                html += "<div id='indexnext'>"
                html += "<a class='button previous' href='"
                if page<=0:
                    html += "#"
                else:
                    html += "/?what=index&page="+str(page-1)
                html += "'>previous</a>"
                html += "<span>"+str(page+1)+"/"+str(maxpage)+"</span>"
                html += "<a class='button next' href='"
                if page>=(maxpage-1):
                    html += "#"
                else:
                    html += "/?what=index&page="+str(page+1)
                html += "'>next</a>"
                html += "</div>"

        else:

            def get_text(node):
                text = ""
                for child in node.childNodes:
                    if child.nodeType == child.TEXT_NODE:
                        text += child.data
                return text

            tgs = []
            objs = []
            try:
                xmlDoc = minidom.parse(os.path.join(self.album,"annotations.xml"))
                if not xmlDoc.documentElement.tagName == "images":
                    raise Exception
                def filter_file(f):
                    return f.getAttribute("file") == self.images[image-1]['file']
                xmlImage = filter(filter_file,xmlDoc.documentElement.getElementsByTagName("image"))[0]
                for tg in xmlImage.getElementsByTagName("tag"):
                    tgs.append(get_text(tg))
                html += "<script language='Javascript'>"
                html += "var annotatedobjects = ["
                for i,obj in enumerate(xmlImage.getElementsByTagName("object")):
                    if i > 0:
                        html += ","
                    temp = {'id':str(get_text(obj)), \
                            'x':int(obj.getAttribute("left")), \
                            'x2':int(obj.getAttribute("right")), \
                            'y':int(obj.getAttribute("top")), \
                            'y2':int(obj.getAttribute("bottom"))}
                    html += str(temp)
                    objs.append(temp)
                    #html += "{id:"+get_text(obj)+","
                    #html += "x:"+obj.getAttribute("left")+","
                    #html += "x2:"+obj.getAttribute("right")+","
                    #html += "y:"+obj.getAttribute("top")+","
                    #html += "y2:"+obj.getAttribute("bottom")+"}"
                html += "];"
                html += "</script>"
            except Exception:
                pass

            html += "<script language='Javascript'>"
            html += "var autocnames = \""+(",".join(self.history))+"\";"
            html += "</script>"

            html += str(image)+"/"+str(len(self.images))+": "+self.images[image-1]['file']+"<br />"
            html += "<div id='images'>"
            html += "<div id='image'>"
            #html += "image "+str(image)+": "+self.images[image-1]['file']+"<br />"
            #html += "<img src='/album/"+urllib.quote(self.images[image-1]['file'])+"' "

            #html += "<img src='/image?file="+self.images[image-1]['file']+"' "
            html += "<img src='/image?file="+str(image-1)+"&r="+str(random.randint(1,999999))+"' "

            #html += "height='"+str(min(self.images[image-1]['height'],1250))+"' "
            html += "/>"
            html += "</div>"

            html += "<div id='thumbs'>"
            for obj in objs:
                w = obj["x2"] - obj["x"] + 1
                h = obj["y2"] - obj["y"] + 1
                s = 75.0 / h;
                if ((s*w)>300):
                    s = 300 / w;
                html += "<div class='thumb' style='width:"+str(int(s*w))+"px; height:"+str(int(s*h))+"px; display:block'>"
                #html += "<img src='/image?file="+self.images[image-1]['file']+"' "
                html += "<img src='/image?file="+str(image-1)+"&r="+str(random.randint(1,999999))+"' "
                html += "style='width:"+str(int(s*self.images[image-1]['width']))+"px; height:"+str(int(s*self.images[image-1]['height']))+"px; "
                html += "margin-left:-"+str(int(s*obj["x"]))+"px; margin-top:-"+str(int(s*obj["y"]))+"px' "
                html += "/>"
                html += "</div>"
                html += "<div class='name'>"+obj["id"]+" <a class='delete' href='#'>X</a></div>"
            html += "<div class='thumb'>"
            #html += "<img src='/image?file="+self.images[image-1]['file']+"' />"
            html += "<img src='/image?file="+str(image-1)+"&r="+str(random.randint(1,999999))+"' />"
            html += "</div>"
            html += "<div id='save'>"
            html += "<input id='saveinput' type='text' value='enter name/id...' />"
            html += "<a class='button' href='#'>ok</a>"
            html += "</div>"
            html += "</div>"
            html += "</div>"

            html += "<div id='tags'>"
            for mainCls,classes in sorted(self.classes.iteritems()):
                html += "<div class='taggroup'>"
                html += "<a href='#' class='"
                if mainCls in tgs:
                    html += "selected"
                html += "'>"+mainCls+"</a>"
                for cls in classes:
                    html += " <a href='#' class='"
                    if cls in tgs:
                        html += "selected"
                    html += "'>"+cls+"</a>"
                html += "</div>"
            html += "</div>"

            html += "<div id='next'>"
            #if image > 1:
            html += "<a class='button previous' href='#'>previous</a>"
            html += "<a class='button index' href='#'>index</a>"
            #if image < len(self.images):
            html += "<a class='button next' href='#'>next</a>"
            html += "<form action='/' method='post'>"
            html += "<input type='hidden' name='image' value='"+str(image)+"' />"
            html += "<input type='hidden' name='tags' value='' />"
            html += "<input type='hidden' name='objects' value='' />"
            html += "<input type='hidden' name='what' value='next' />"
            html += "<input type='hidden' name='page' value='"+str(int(math.floor((image-1)/20.0)))+"' />"
            #html += "<input type='submit' value='next' />"
            html += "</form>"
            html += "</div>"

        html += self.footer
        return html

    index.exposed = True


def get_local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(("google.com", 80))
    return s.getsockname()[0]


def start_server():
    ip = get_local_ip()
    print ip

    folder = os.path.dirname(__file__)
    if not folder:
        folder = os.path.abspath(os.path.dirname(inspect.getfile(sys._getframe(1))))

    #app = cherrypy.tree.mount(Annotator(), '/', config=os.path.join(os.path.dirname(__file__), 'annotator.conf'))
    app = cherrypy.tree.mount(Annotator(), '/', config=os.path.join(folder, 'annotator.conf'))
    #app.merge({'/static/design': {'tools.staticdir.on':True, 'tools.staticdir.dir':os.path.join(os.path.dirname(__file__),"design")}})
    app.merge({'/static/design': {'tools.staticdir.on':True, 'tools.staticdir.dir':os.path.join(folder,"design")}})
    #app.merge({'/album': {'tools.staticdir.on':True, 'tools.staticdir.dir':albumDir}})

    #cherrypy.config.update(os.path.join(os.path.dirname(__file__), 'annotator.conf'))
    cherrypy.config.update(os.path.join(folder, 'annotator.conf'))
    cherrypy.config.update({'server.socket_host':ip, 'server.socket_port': 80})

    #cherrypy.server.quickstart()
    cherrypy.engine.start()


if __name__ == "__main__":
    start_server()
